﻿using System;
using System.Collections.Specialized;
using System.Data;
using System.Globalization;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;
using System.Configuration;

using BizElements.Core;
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>Defines a context in which a web form is openned, transfers data between forms and initializes domain objects' properties.</summary>
    /// <remarks><para>Represent a single object that encapsulates all data required for application controler logic such as: URL, form parameters, UI messages etc.</para>
    /// <para>Commonly used pages are configured in <b>appSettings</b> section of Web.config file:
    /// <code>
    /// &lt;add key="BizElements.Web.WebFormContext.DefaultPageUrl" value="~/IntroFrm.aspx"/&gt;
    /// </code>
    /// </para>
    /// <para>Implementation logic, ie. how context is transferred between pages and where it's stored, is provided by manager configured in <b>appSettings</b> section of Web.config file. Example:
    /// <code>
    /// &lt;add key="BizElements.Web.WebFormContext.ManagerAssembly" value="BizElements.Web"/&gt;
    /// &lt;add key="BizElements.Web.WebFormContext.ManagerClass" value="BizElements.Web.WebFormContextManager"/&gt;
    /// </code>
    /// </para></remarks>
    public sealed class WebFormContext
    {
        #region Constants.

        /// <summary>GET parameter used to send a unique web context identifier in query string.</summary>
        public static readonly string ContextQueryKey = "guid";
        /// <summary>Reload parent/opener page. Used when popup form modifies data displayed by parent page.</summary>
        const string JsReloadOpener = "window.opener.location.reload();";

        #endregion

        #region Configuration.

        /// <summary>Relative URL of default/intro form.</summary>
        /// <value>URL configured in <b>BizElements.Web.WebFormContext.DefaultPageUrl</b> app setting. Default is <b>"~/Default.aspx"</b>.</value>
        public static readonly string DefaultPageRelativeUrl = ConfigurationManager.AppSettings["BizElements.Web.WebFormContext.DefaultPageUrl"] ?? @"~/Default.aspx";

        static readonly IWebFormContextManager Manager;
        
        static WebFormContext()
        {
            Manager = ReflectionUtil.TryCreateInstanceFromAppSettings("BizElements.Web.WebFormContext.ManagerAssembly", "BizElements.Web.WebFormContext.ManagerClass") as IWebFormContextManager;
            if (Manager == null)
                Manager = new WebFormContextManager();
        }

        #endregion

        #region CTor.

        /// <summary>Initializes a new instance of WebFormContext class.</summary>
        public WebFormContext()
        {
            this.ContextId = Guid.NewGuid().ToString();
            this.Parameters = new NameValueCollection();
            this.ReturnPropertyMap = new NameValueCollection();
            this.PropertyToControlMap = new NameValueCollection();
        }

        /// <summary>Initializes a new instance of <b>WebFormContext</b>.</summary>
        /// <param name="url">Form URL that provides data for web context properties.</param>
        public WebFormContext(string url)
            : this()
        {
            this.Url = url;
        }

        /// <summary>Initializes a new instance of <b>WebFormContext</b>.</summary>
        /// <param name="url">Form URL that provides data for web context properties.</param>
        /// <param name="entityId">ID of entity/object managed by a web form.</param>
        public WebFormContext(string url, object entityId)
            : this()
        {
            this.Url = url;
            this.EntityId = Convert.ToString(entityId, CultureInfo.InvariantCulture);
        }

        #endregion

        #region Properties.

        /// <summary>Unique context ID.</summary>
        public string ContextId { get; set; }

        /// <summary>Web form URL with context data.</summary>
        public string Url { get; set; }

        /// <summary>Form title.</summary>
        public string Title { get; set; }

        /// <summary>Defines how a form is opened/displayed.</summary>
        public OpenFormMode Mode { get; set; }

        /// <summary>Defines a form size.</summary>
        public WindowSize Size { get; set; }

        /// <summary>ID of entity/object managed by a web form.</summary>
        public string EntityId { get; set; }

        /// <summary>Return URL if applicable.</summary>
        public string ReturnUrl { get; set; }

        /// <summary>Title of form to which a user is returned after the current form is closed.</summary>
        public string ReturnTitle { get; set; }

        /// <summary>Additional parameters, such as values for entity's properties.</summary>
        public NameValueCollection Parameters { get; private set; }

        /// <summary>Maps entity properties passed between two forms which manage them. Typically used to bind properties maped to foreign and primary keys.</summary>
        public NameValueCollection ReturnPropertyMap { get; private set; }

        /// <summary>Maps properties of object opened in popup window to controls of windows opener. Typically used to generate on-close script that copies parent entity properties created in popup window.</summary>
        public NameValueCollection PropertyToControlMap { get; private set; }

        /// <summary>Defines whether the entity be edited.</summary>
        public bool ReadOnly { get; set; }

        /// <summary>Defines whether to hide controls which display/edit entity's children.</summary>
        public bool ChildrenHidden { get; set; }

        /// <summary>Defines whether window opener should be reloaded when popup is close.</summary>
        public bool ReloadOpener { get; set; }

        #endregion

        #region Open.

        /// <summary>Builds URL, pushes context to user stack and opens web form.</summary>
        public void Open()
        {
            string url = Manager.BuildUrl(this);
            Manager.Push(this);
            Manager.OpenUrl(this.Mode, url, this.Size);
        }

        #endregion

        #region Close.

        /// <summary>Closes the current form.</summary>
        /// <param name="status">Return status of operation executed on the form.</param>
        /// <param name="returnParams">Return values for the form to which the user is redirected to.</param>
        /// <param name="message">Optional UI message to be displayed on the form to which the user is redirected to.</param>
        [Obsolete("Prefer using Close(CloseFormStatus, object, Delegates.Procedure, string) method overload. Generate sample code with latest BizElements 'AspxCodeBehind.NET' template to see the recommended usage pattern.")]
        public void Close(CloseFormStatus status, NameValueCollection returnParams, string message)
        {
            if (this.Mode == OpenFormMode.Redirect)
                RedirectBack(returnParams, message);
            else
                Manager.ClosePopup(this, null);
        }

        /// <summary>Closes the current form.</summary>
        /// <param name="status">Return status of operation executed on the form.</param>
        /// <param name="bizObject">Optional object from which return values are exctracted and passed to caller, ie. previous, form.</param>
        /// <param name="cleanUp">Optional clean-up method that frees resources, such as session, used by form being closed.</param>
        /// <param name="message">Optional message to display after the form has been closed. Next form in chain is responsible to display the message.</param>
        public void Close(CloseFormStatus status, object bizObject, Delegates.Procedure cleanUp, string message)
        {
            if (this.Mode == OpenFormMode.Redirect)
            {
                NameValueCollection returnParams = (bizObject != null) ? CreateReturnParams(bizObject) : null;
                if (cleanUp != null)
                    cleanUp();

                RedirectBack(returnParams, message);
            }
            else
            {
                string setOpenerControls = (bizObject != null) ? CreateCopyPropertiesToControlsScript(bizObject) : null;
                if (cleanUp != null)
                    cleanUp();

                Manager.ClosePopup(this, setOpenerControls);
            }
        }

        private void RedirectBack(NameValueCollection returnParams, string message)
        {
            string url = Manager.GetReturnUrl(this, message);
            if (returnParams != null)
            {
                foreach (string key in returnParams.AllKeys)
                {
                    if (!string.IsNullOrEmpty(returnParams[key]))
                        url = WebUtil.SetUrlParam(url, key, returnParams[key]);
                }
            }

            HttpContext.Current.Response.Redirect(url);
        }

        private string CreateCopyPropertiesToControlsScript(object bizObject)
        {
            StringBuilder jsCopyProperties = new StringBuilder();
            foreach (string propertyName in this.PropertyToControlMap.AllKeys)
            {
                string controlName = this.PropertyToControlMap[propertyName];
                object value = ReflectionUtil.GetPublicPropertyValue(bizObject, propertyName);
                jsCopyProperties.AppendFormat("window.opener.document.getElementById('{0}').value = '{1}';", controlName, value);
            }

            return jsCopyProperties.ToString();
        }

        #endregion

        #region ParseEntityProperties.

        /// <summary>Initializes entity properties with values stored in Parameters collection.</summary>
        /// <param name="entity">Entity whose properties are parsed.</param>
        /// <param name="criteria">Selects the group of properties to parse if the value is provided in Parameters collection.</param>
        public void ParseEntityProperties(IEntity entity, PropertySelection criteria)
        {
            Manager.ParseEntityProperties(entity, this.Parameters, criteria);
        }

        #endregion

        #region EnsureFormContext, ParseFromUrl.

        /// <summary>Initializes context if not alread initialized.</summary>
        /// <param name="formContext">Variable or field to initialize if not already initialized, ie. if it point to a nul reference.</param>
        /// <param name="uiWebFormContext">Hidden form field that contains XML serialized context data.</param>
        public static void EnsureFormContext(ref WebFormContext formContext, HiddenField uiWebFormContext)
        {
            Manager.EnsureFormContext(ref formContext, uiWebFormContext);
        }

        /// <summary>Initializes context if not alread initialized.</summary>
        /// <param name="formContext">Variable or field to initialize if not already initialized, ie. if it point to a nul reference.</param>
        /// <param name="uiWebFormContext">Hidden form field that contains XML serialized context data.</param>
        public static void EnsureFormContext(ref WebFormContext formContext, ITextControl uiWebFormContext)
        {
            Manager.EnsureFormContext(ref formContext, uiWebFormContext);
        }

        #endregion

        #region CreateReturnParams.

        /// <summary>Uses <b>ReturnPropertyMap</b> to create parameters for the form to which a user is redirected.</summary>
        /// <param name="bizObject">Entity which is managed by the current form.</param>
        public NameValueCollection CreateReturnParams(object bizObject)
        {
            NameValueCollection returnArgs = new NameValueCollection();
            foreach (string returnProperty in this.ReturnPropertyMap.AllKeys)
            {
                string callerPropName = this.ReturnPropertyMap[returnProperty];
                object retval = ReflectionUtil.GetPublicPropertyValue(bizObject, returnProperty);
                if (retval != null)
                    returnArgs.Add(callerPropName, retval.ToString());
            }

            return returnArgs;
        }

        #endregion

        /// <summary>Builds URL based on data provided in the context.</summary>
        public string BuildUrl()
        {
            return Manager.BuildUrl(this);
        }
    }
}
